package com.qf.service.impl;

import com.qf.entity.Hotal;
import com.qf.entity.SearchParams;
import com.qf.service.ISearchHotalService;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.*;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.script.Script;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.search.sort.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.ScriptField;
import org.springframework.stereotype.Service;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.*;

@Service
public class SearchHotalServiceImpl implements ISearchHotalService {

    @Autowired
    private ElasticsearchRestTemplate restTemplate;

    /**
     * 判断客房这天是否已经预订满了的脚本
     */
    private String script1 = "doc['rooms.prices.number'].value == doc['rooms.prices.hasNumber'].value";

    /**
     * 计算酒店平均值的脚本
     */
    private String avgScript = "double avgPrice = Integer.MAX_VALUE;\n" +
            "          boolean flag = true;\n" +
            "          for(room in params._source.rooms){\t\n" +
            "          \tdouble roomAvgPirce = 0;\n" +
            "          \tint days = 0;\n" +
            "          \tfor(priceInfo in room.prices){\n" +
            "          \t\tif(priceInfo.date >= params.beginTime && priceInfo.date < params.endTime){\n" +
            "          \t\t\tif(priceInfo.number == priceInfo.hasNumber){\n" +
            "          \t\t\t\tflag = false;\n" +
            "          \t\t\t}\n" +
            "          \t\t\troomAvgPirce += priceInfo.price;\n" +
            "          \t\t\tdays++;\n" +
            "          \t\t}\n" +
            "          \t}\n" +
            "          \troomAvgPirce = roomAvgPirce / days;\n" +
            "          \tif(roomAvgPirce >= params.minPrice && roomAvgPirce <= params.maxPrice && flag){\n" +
            "          \t\tavgPrice = Math.min(avgPrice, roomAvgPirce);\n" +
            "          \t}\n" +
            "          \tflag = true;\n" +
            "          }\n" +
            "          return avgPrice == Integer.MAX_VALUE ? -1 : avgPrice;";

    /**
     * 计算距离的自定义字段
     */
    private String disScript = "doc.location.planeDistance(params.lat, params.lon) / 1000";

    /**
     * 搜索酒店信息
     * @param searchParams
     * @return
     */
    @Override
    public List<Hotal> searchHotal(SearchParams searchParams) {

        //--------------------核心查询----------------------
        //主查询 - 主要查询符合条件的酒店信息
        BoolQueryBuilder primaryQuery = QueryBuilders.boolQuery();

        //判断是否输入了入住和离店时间
        if (searchParams.getBeginTime() != null && searchParams.getEndTime() != null) {

            //
            BoolQueryBuilder priceBool = QueryBuilders.boolQuery()
                    .must(QueryBuilders
                            .rangeQuery("rooms.prices.date")
                            .gte(searchParams.getBeginTime().getTime())
                            .lt(searchParams.getEndTime().getTime()))
                    .must(QueryBuilders.scriptQuery(new Script(script1)));

            //客房某天预订信息的嵌套查询
            NestedQueryBuilder roomNestQuery = QueryBuilders.nestedQuery("rooms.prices", priceBool, ScoreMode.Avg);

            //客房嵌套查询中的bool查询
            BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
                    .mustNot(roomNestQuery);

            //客房的嵌套查询
            NestedQueryBuilder roomsQuery = QueryBuilders.nestedQuery("rooms", boolQuery, ScoreMode.Avg);

            //是否有空余房间的匹配查询
            primaryQuery.must(roomsQuery);
        }

        //判断关键词是否为空
        if (!StringUtils.isEmpty(searchParams.getKeyword())) {
            //关键字匹配
            primaryQuery
                    .should(QueryBuilders
                            .multiMatchQuery(searchParams.getKeyword())
                            .field("hotalName", 3)
                            .field("hotalName.pinyin")
                            .field("hotalInfo")
                            .field("hotalInfo.pinyin")
                            .field("keyword", 2)
                            .field("keyword.pinyin")
                            .field("brand", 2)
                            .field("brand.pinyin")
                            .field("address")
                            .field("address.pinyin")
                            .field("reg")
                            .field("reg.pinyin")
                    ).should(QueryBuilders
                    .nestedQuery("rooms",
                            QueryBuilders.multiMatchQuery(searchParams.getKeyword())
                                    .field("rooms.title")
                                    .field("rooms.title.pinyin"),
                            ScoreMode.Avg))
                    .minimumShouldMatch(1);
        }


        //二次查询 - 查询当前城市的酒店
        NestedQueryBuilder cityQuery = QueryBuilders.nestedQuery("city",
                QueryBuilders.termQuery("city.cityName.keyword", searchParams.getCityName()),
                ScoreMode.Avg);


        //最外层的查询 - Boosting查询
        //主查询 - 查询符合条件的酒店
        //二次查询 - 在符合条件的酒店中判断城市，如果是当前城市的酒店，评分增加
        BoostingQueryBuilder executeQuery = QueryBuilders
                .boostingQuery(primaryQuery, cityQuery)
                .negativeBoost(5);

        //-------------------自定义脚本字段--------------------------
        Map<String, Object> params = new HashMap<>();
        params.put("beginTime",
                searchParams.getBeginTime() != null ? searchParams.getBeginTime().getTime() : Long.MIN_VALUE);
        params.put("endTime",
                searchParams.getEndTime() != null ? searchParams.getEndTime().getTime() : Long.MAX_VALUE);
        params.put("minPrice",
                searchParams.getMinPrice() != null ? searchParams.getMinPrice() : 0);
        params.put("maxPrice",
                searchParams.getMaxPrice() != null ? searchParams.getMaxPrice() : 10000);
        params.put("lat",
                searchParams.getLat());
        params.put("lon",
                searchParams.getLon());

        System.out.println("自定义脚本字段的参数值：" + params);

        //平均价格的自定义字段
        ScriptField scriptField = new ScriptField("avgPrice",
                new Script(ScriptType.INLINE, "painless", avgScript, params));

        //距离的自定义字段
        ScriptField scriptField2 = new ScriptField("distance",
                new Script(ScriptType.INLINE, "painless", disScript, params));

        //-----------------------------处理排序的问题-----------------------------------
        List<SortBuilder> sortBuilders = new ArrayList<>();

        switch (searchParams.getSortType()) {
            case 1:
                //智能排序
                ScoreSortBuilder scoreSortBuilder = SortBuilders.scoreSort();
                sortBuilders.add(scoreSortBuilder);
                break;
            case 2:
                //价格排序
                ScriptSortBuilder priceSortBuilder = SortBuilders.scriptSort(new Script(ScriptType.INLINE, "painless", avgScript, params), ScriptSortBuilder.ScriptSortType.NUMBER);
                sortBuilders.add(priceSortBuilder);
                break;
            case 3:
                //离我最近
                GeoDistanceSortBuilder sortBuilder = SortBuilders.geoDistanceSort("location", searchParams.getLat(), searchParams.getLon()).order(SortOrder.ASC);
                sortBuilders.add(sortBuilder);
                break;
        }

        //生成一个评分加强 - 点击率加强评分
        Set<FunctionScoreQueryBuilder.FilterFunctionBuilder> scoreFunctionSet = Collections.singleton(
                new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                        ScoreFunctionBuilders.fieldValueFactorFunction("djl").setWeight(2)));

        FunctionScoreQueryBuilder scoreQuery = QueryBuilders.functionScoreQuery(executeQuery,
                scoreFunctionSet.toArray(new FunctionScoreQueryBuilder.FilterFunctionBuilder[0]));

        //执行查询
        return search(scoreQuery, sortBuilders, scriptField, scriptField2);
    }


    /**
     * 业务层内置的搜索方法
     */
    private List<Hotal> search(QueryBuilder queryBuilder, List<SortBuilder> sortBuilders, ScriptField... scriptFields){

        //构建原生的搜索对象
        NativeSearchQuery query = new NativeSearchQuery(queryBuilder, null, sortBuilders);

        //设置自定义脚本字段
        if (scriptFields != null) {
            //因为有了自定义字段，需要设置其他显示的字段
            query.addScriptField(scriptFields);
            //设置需要显示的字段
            String[] exclude = {"rooms"};
            query.addSourceFilter(new FetchSourceFilter(null, exclude));
        }

        //执行搜索
        SearchHits<Hotal> searchHits = restTemplate.search(query, Hotal.class);

        //搜索结果结合
        List<Hotal> hotals = new ArrayList<>();

        //格式化对象
        NumberFormat numberFormat = new DecimalFormat("0.00");

        //获得搜索结果
        searchHits.stream().forEach(hotalSearchHit -> {
            //循环获取搜索出来的酒店信息
            Hotal hotal = hotalSearchHit.getContent();
            //判断酒店价格是否符合均价，如果不符合则不显示
            if (hotal.getAvgPrice() != -1) {
                //格式化酒店的平均价格
                hotal.setAvgPrice(Double.parseDouble(numberFormat.format(hotal.getAvgPrice())));
                //格式化酒店的距离
                hotal.setDistance(Double.parseDouble(numberFormat.format(hotal.getDistance())));

                hotals.add(hotal);
            }
        });

        return hotals;
    }
}
